Skip to content

Deploy Prompter on the Cratis cluster, and plan the docs-gap and GitHub surfaces - #2

Merged
woksin merged 5 commits into
mainfrom
deploy/release-story-and-docs-gap
Aug 6, 2026
Merged

Deploy Prompter on the Cratis cluster, and plan the docs-gap and GitHub surfaces#2
woksin merged 5 commits into
mainfrom
deploy/release-story-and-docs-gap

Conversation

@woksin

@woksin woksin commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Prompter's first release: the bot can now be deployed to the Cratis UpCloud Kubernetes cluster from a released version.

Added

  • Pulumi stack running Prompter on the existing UpCloud Kubernetes cluster: namespaced workload, in-cluster Postgres with pgvector, and an ingress publishing only POST /reindex (P-21).
  • Deploy - Production workflow that pins the released image tag, applies the stack, and redeploys or rolls back any version on demand (P-21).

Changed

  • Publish now deploys the version it just pushed, and skips cleanly while the cluster side is unconfigured (P-21).
  • Deployment runbook documents how a release is actually cut: a merged pull request labeled major, minor or patch, or a manual dispatch with an explicit version (P-26).

woksin and others added 5 commits August 6, 2026 11:21
Deployment code, not a deployment: a Pulumi C# project modeled on Studio's,
which is the reference implementation for this cluster. It keeps Studio's
conventions deliberately — self-managed file://./state committed to Git, the
passphrase secrets provider, and a scripts/set-secrets.sh for the runtime
secrets.

The one place it departs is ownership. Studio's stack creates the UKS cluster,
the NGINX controller, the cert-manager issuer and the upcloud-maxiops
StorageClass; this stack looks the cluster up by id and declares nothing
cluster-scoped, so everything it creates lives inside the prompter-production
namespace and the two stacks cannot collide (D-15).

What it provisions: Postgres with pgvector as a single-replica StatefulSet
(same image as local compose and the eval workflow), the bot as a single-replica
Deployment with the Recreate strategy — the Discord gateway wants exactly one
connection — and an ingress that publishes only POST /reindex, leaving /healthz
cluster-internal.

Probe asymmetry is intentional: readiness uses /healthz (database + gateway),
liveness is a TCP check, because /healthz reports unhealthy during a Discord
outage and restart-looping would fix nothing while taking the re-index endpoint
down with it.

Builds clean in the solution; nothing has been applied to the cluster.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LV2femm3ZR47Lk2SscwJb
deploy-production.yml mirrors Studio's: pin the image tag with `pulumi config
set` (which preserves the config file's comments, unlike a raw YAML edit), run
`pulumi up` against the file backend on the self-hosted cratis runner, then
commit the updated state back with [skip ci].

Publish calls it as a workflow_call after publish-docker, so a failed image push
can never deploy a tag that does not exist. workflow_dispatch stays available for
redeploying or rolling back to any version by hand.

Needs two repository secrets that do not exist yet: PULUMI_CONFIG_PASSPHRASE and
UPCLOUD_TOKEN.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LV2femm3ZR47Lk2SscwJb
Three things the planning docs were missing or had wrong.

Release mechanics: nothing has ever been released. cratis/release-action only
cuts a release for a merged PR labeled major/minor/patch, PR #1 carried none, so
Publish ran, decided should-publish=false and did nothing — successfully, which
is why it went unnoticed. Docker Hub has no cratis/prompter repository and there
are no releases. DEPLOYMENT.md now documents the label rule and how to cut the
first version.

D-15 answers Q-5 and reverses D-11's recommendation: Prompter's Pulumi code
lives here, not in Studio's stack. Reading Studio's deploy workflow is what
settled it — it pins one version across every Studio image, so a prompterImage
entry there would need its own workflow and a cross-repo dispatch anyway, while
making every Prompter release re-evaluate MongoDB, Chronicle and the AuthProxies.
Marked OPEN: it is their cluster, so the team confirms.

Docs-gap flywheel (P-33) rewritten around its two possible feeds, because the
idea of Prompter filing docs-gap issues runs straight into D-13 — the interaction
log keeps no question text, so there is nothing to mine today. Feed A is a
"this should be documented" button (P-45): the click is the consent, the text is
forwarded and never stored, and D-13 stands untouched. Feed B needs D-14, added
as an OPEN decision recommending A first. P-44 adds the GitHub issues surface —
the same answering behind a webhook, silent on refusal — which also closes the
loop cheaply, since a refusal on an issue is a docs gap already in a tracker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LV2femm3ZR47Lk2SscwJb
The self-hosted runner label queues indefinitely rather than failing when it is
not available to a repository, and a `production` environment with required
reviewers pauses the deploy. Both are fine — they just should not be discovered
during the first release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LV2femm3ZR47Lk2SscwJb
The stack has never been applied and its secrets may not exist yet, so calling
it from Publish would turn every release red until the cluster side is set up.
A `secrets` context cannot be read from a job-level `if`, so a small job checks
the two secrets plus an initialized Deployment/state and the deploy is gated on
its output. Publishing an image stays green on its own; the skip is reported as
a notice naming exactly what is missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LV2femm3ZR47Lk2SscwJb
@woksin woksin added the minor For minor releases - according to SemVer label Aug 6, 2026
@woksin

woksin commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Reviewer context — none of this is user-facing, so it is here rather than in the body.

Where the deployment code lives. D-11 recommended making Prompter a workload in Studio's Deployment/ stack. Reading that stack reversed it, and D-15 records why: Studio's deploy-production.yml pins one version across every Studio image (so a prompterImage entry needs its own workflow and a cross-repo dispatch anyway), and a shared stack would make each Prompter release re-evaluate MongoDB, Chronicle, Vault and both AuthProxies. D-15 is marked open — it is the platform team's cluster, so please confirm. Reversal is cheap by construction: every resource class takes Provider + Namespace exactly like Studio's own services, so moving them into Studio/Deployment/Services is a Program.cs wiring change.

This stack owns nothing cluster-scoped. The cluster, node group, NGINX controller, cert-manager ClusterIssuer, upcloud-maxiops StorageClass and Promtail all stay Studio's; Prompter looks the cluster up by id and creates only namespaced resources. That rule is what keeps two stacks on one cluster from colliding.

Probe asymmetry is deliberate. Readiness uses /healthz (database + gateway). Liveness is a TCP check, because /healthz reports unhealthy during a Discord outage and restart-looping would neither fix Discord nor keep the re-index endpoint available.

Nothing has been applied. This is unrun infrastructure code. Before a real deploy: PULUMI_CONFIG_PASSPHRASE and UPCLOUD_TOKEN secrets, the clusterId and ingressHost config values, a DNS record at the cluster load balancer, scripts/set-secrets.sh with the five runtime secrets, and pulumi stack init production. The Publish → deploy hand-off is gated on those existing, so merging this cannot break a release. Backups are not wired: the corpus is rebuildable from cratis.io and the interaction log is anonymous rows (D-13), which is stated in the runbook rather than left implied.

Planning changes ride along. P-33 (docs-gap flywheel) is rewritten around its two possible feeds, since filing issues about undocumented questions runs straight into D-13 — the interaction log keeps no question text, so there is nothing to mine today. P-45 is the unblocked feed (a "this should be documented" button; the click is the consent, nothing is stored), P-44 adds a GitHub-issues answering surface, and D-14 records the genuinely open question — whether question text is stored at all — recommending "not yet". Q-7 asks which repository receives a docs-gap issue.

Verification. dotnet build -c Release 0 warnings, dotnet test -c Release 278 specs green, workflow YAML and shell parse, all new documentation links and anchors resolve, Documentation/ lints clean. The repository tracks work as P-numbers in Planning/BACKLOG.md rather than GitHub issues, which is what the body's references point at.

@woksin
woksin merged commit 889e09a into main Aug 6, 2026
2 checks passed
@woksin
woksin deleted the deploy/release-story-and-docs-gap branch August 6, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor For minor releases - according to SemVer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant